package com.dd.cloud.common.utils;

import com.google.common.base.Joiner;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.*;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * @author zk
 */
public class RsaUtils {

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;
    /**
     * 加密算法
     */
    public static final String KEY_ALGORITHM = "RSA";
    /**
     * 签名算法
     */
    public static final String SIGN_ALGORITHM = "SHA1WithRsa";

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径，相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        String key = new String(bytes, StandardCharsets.UTF_8).replace("-----BEGIN PUBLIC KEY-----", "");
        key = key.replace("-----END PUBLIC KEY-----", "");
        bytes = Base64.getMimeDecoder().decode(key.getBytes(StandardCharsets.UTF_8));
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取公钥
     *
     * @param is 秘钥文件流
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(InputStream is) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int len;
        while ((len = is.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }
        bytes = bos.toByteArray();
        bos.close();
        is.close();
        String key = new String(bytes, StandardCharsets.UTF_8).replace("-----BEGIN PUBLIC KEY-----", "");
        key = key.replace("-----END PUBLIC KEY-----", "");
        bytes = Base64.getMimeDecoder().decode(key.getBytes(StandardCharsets.UTF_8));
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径，相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        String key = new String(bytes, StandardCharsets.UTF_8).replace("-----BEGIN PRIVATE KEY-----", "");
        key = key.replace("-----END PRIVATE KEY-----", "");
        bytes = Base64.getMimeDecoder().decode(key.getBytes(StandardCharsets.UTF_8));
        return getPrivateKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param is 秘钥文件流
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(InputStream is) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int len;
        while ((len = is.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }
        bytes = bos.toByteArray();
        bos.close();
        is.close();
        String key = new String(bytes, StandardCharsets.UTF_8).replace("-----BEGIN PRIVATE KEY-----", "");
        key = key.replace("-----END PRIVATE KEY-----", "");
        bytes = Base64.getMimeDecoder().decode(key.getBytes(StandardCharsets.UTF_8));
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        return factory.generatePublic(spec);
    }

    /**
     * RSA公钥加密
     * @param str 加密字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     *
     */
    public static String encrypt( String str, String publicKey ){
        try {
            //base64编码的公钥
            byte[] decoded = org.apache.commons.codec.binary.Base64.decodeBase64(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
            //RSA加密
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] strBytes = str.getBytes("UTF-8");
            int inputLength = strBytes.length;
            System.out.println("加密字节数：" + inputLength);
            // 最大加密字节数，超出最大字节数需要分组加密
            // 标识
            int offSet = 0;
            byte[] resultBytes = {};
            byte[] cache;
            while (inputLength - offSet > 0) {
                if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(strBytes, offSet, MAX_ENCRYPT_BLOCK);
                    offSet += MAX_ENCRYPT_BLOCK;
                } else {
                    cache = cipher.doFinal(strBytes, offSet, inputLength - offSet);
                    offSet = inputLength;
                }
                resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
                System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
            }
            String outStr = org.apache.commons.codec.binary.Base64.encodeBase64String(resultBytes);
            return outStr;
        }catch (Exception e){
            System.out.println("rsaEncrypt error:" + e.getMessage());
        }
        return "";
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文，生成rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }

    /**
     * 生成数字签名
     *
     * @param text
     * @param keyPath
     * @return
     */
    public static String privateSign(String text, InputStream keyPath) {
        try {
            Signature sign = Signature.getInstance(SIGN_ALGORITHM);
            sign.initSign(getPrivateKey(keyPath));
            sign.update(text.getBytes(StandardCharsets.UTF_8));
            byte[] bytes = sign.sign();
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 数字签名验签
     *
     * @param text
     * @param signText
     * @param keyPath
     * @return
     */
    public static boolean publicSignVerify(String text, String signText, InputStream keyPath) {
        try {
            Signature sign = Signature.getInstance(SIGN_ALGORITHM);
            sign.initVerify(getPublicKey(keyPath));
            sign.update(text.getBytes(StandardCharsets.UTF_8));
            System.out.println("参数签名串:" + signText );
            System.out.println("加密签名串:" + sign.toString() );

            return sign.verify(Base64.getDecoder().decode(signText));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 对象转String排序
     *
     * @param obj
     * @return
     */
    public static String objToStringSort(Object obj) {
        Class<?> cls = obj.getClass();
        Map<String, String> map = new LinkedHashMap<>();
        while (cls != null && cls != Object.class) {
            Field[] fields = cls.getDeclaredFields();
            for (Field item : fields) {
                if (!item.isAccessible()) {
                    item.setAccessible(true);
                }
                try {
                    if (item.get(obj) != null) {
                        String value = String.valueOf(item.get(obj));
                        map.put(item.getName(), value);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            cls = cls.getSuperclass();
        }
        List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(map.entrySet());
        //排序
        infoIds.sort(Map.Entry.comparingByKey());
        String result = Joiner.on("&").join(infoIds);
        System.out.println("签名串:" + result);
        return result;
    }
}